package demo3

import (
	"fmt"
	"os"
	"sync"
)

func demo371() {
	fmt.Println("defer begint")
	// 将 defer 逐个放入调用栈
	// 第一个最后执行
	defer fmt.Println("defer 111")
	defer fmt.Println("defer 222")
	// 最后一个最先执行
	defer fmt.Println("defer 3333")

	fmt.Println("defer end")
}

// lock 和 unlock 保证变量的正确访问
func demo372() {
	var (
		// 演示用的 map
		valueByKey = make(map[string]int)
		// 保证使用映射时的并发安全的互斥锁
		// map 默认不是并发安全的，准备一个 sync.Mutex 互斥量保护 map 的访问。
		valueByKeyGuard sync.Mutex
	)

	valueByKey["name"] = 11
	valueByKey["age"] = 22
	valueByKey["city"] = 33

	// 根据键读取值
	func(key string) int {
		// 对共享资源加锁
		valueByKeyGuard.Lock()
		// 取值
		v := valueByKey[key]
		fmt.Println(v)
		// 解锁
		valueByKeyGuard.Unlock()

		return v
	}("name")
}

// defer 优化上面的代码
func demo373() {
	var (
		// 演示用的 map
		valueByKey = make(map[string]int)
		// 保证使用映射时的并发安全的互斥锁
		valueByKeyGuard sync.Mutex
	)

	valueByKey["name"] = 44
	valueByKey["age"] = 55
	valueByKey["city"] = 66

	// defer 使用
	func(key string) int {
		valueByKeyGuard.Lock()

		// defer后面的语句不会马上调用, 而是延迟到函数结束时调用
		// 该语句不会马上执行，而是等 readValue() 返回时才会被执行。
		defer valueByKeyGuard.Unlock()

		fmt.Println(valueByKey["age"])
		return valueByKey[key]
	}("name")
}

// 根据文件名查询其大小
func demo374(fileName string) int64 {
	// 文件名打开文件，返回文件、可能错误
	f, error := os.Open(fileName)

	// 打开时发生错误，返回 0
	if error != nil {
		return 0
	}

	// 获取文件的状态信息
	info, err := f.Stat()

	// 发生错误，则关闭文件，返回 0
	if err != nil {
		f.Close()
		return 0
	}

	// 获取文件大小
	size := info.Size()

	// 关闭文件
	f.Close()

	// 返回大小
	return size
}

func demo375(fileName string) int64 {
	f, err := os.Open(fileName)

	if err != nil {
		return 0
	}

	// 延迟调用 close 测试 close 不会执行
	defer f.Close()

	info, err := f.Stat()

	if err != nil {
		// defer 机制触发，调用 Close 关闭文件
		return 0
	}

	size := info.Size()

	// defer 机制触发，调用 Close 关闭文件
	return size
}

// TestDemo37 defer 语句的使用: 允许我们推迟到函数返回之前（或任意位置执行 return 语句之后）一刻才执行某个语句或函数
func TestDemo37() {
	// demo1: 多个 defer 的执行顺序： 类似栈，即后进先出
	demo371()

	/*
		处理业务或逻辑中涉及成对的操作是一件比较烦琐的事情，
		比如打开和关闭文件、接收请求和回复请求、加锁和解锁等。在这些操作中，最容易忽略的就是在每个函数退出处正确地释放和关闭资源。
		defer 语句正好是在函数退出时执行的语句，所以使用 defer 能非常方便地处理资源释放问题。
	*/

	// demo2: 使用延迟并发解锁
	// 在下面的例子中会在函数中并发使用 map，为防止竞态问题，使用 sync.Mutex 进行加锁，参见下面代码：
	// unlock 和 lock 方式正确的访问和返回
	demo372()

	// defer 方式实现正确的访问和返回, 优化代码
	demo373()

	// demo3: 使用延迟释放文件句柄
	// 文件的操作需要经过打开文件、获取和操作文件资源、关闭资源几个过程，
	//	如果在操作完毕后不关闭文件资源，进程将一直无法释放文件资源。
	//	在下面的例子中将实现根据文件名获取文件大小的函数，函数中需要打开文件、获取文件大小和关闭文件等操作。
	//	由于每一步系统操作都需要进行错误处理，而每一步处理都会造成一次可能的退出，
	//	因此就需要在退出时释放资源，而我们需要密切关注在函数退出处正确地释放文件资源。

	// 根据文件名查询其大小
	// 1. 按流程使用 os 操作文件
	fmt.Println(demo374("hello.go"))
	// 2. 使用 defer 操作文件
	fmt.Println(demo375("demo3/demo31.go"))
}
